package jmine.tec.services.runner;

import java.util.Collections;
import java.util.List;
import java.util.Map;

import jmine.tec.services.ServicesController;
import jmine.tec.services.api.ActionsEnum;
import jmine.tec.services.api.ServiceExecutionException;
import jmine.tec.services.api.ServiceProperties;
import jmine.tec.services.api.ServicesPage;
import jmine.tec.services.api.execution.ExecutorParams;
import jmine.tec.services.api.execution.ExecutorTransactionPolicy;
import jmine.tec.services.api.execution.ExecutorValidationPolicy;
import jmine.tec.services.api.factory.ServiceCreationException;
import jmine.tec.services.export.impl.ExportUtils;
import jmine.tec.services.io.ServiceBean;
import jmine.tec.test.Builder;
import bancosys.tec.persist.exception.PersistenceException;

/**
 * Objeto que providencia a execução de uma pequena lista de serviços, com finalidade de facilitar a elaboração de testes de serviços
 * 
 * @author lundberg
 */
public class ServiceRunner {

    private final ServicesController controller;

    private ServicesPage services;

    private ServiceProperties parentProperties;

    private ServiceBean current;

    private ExecutorParams executorParams = new ExecutorParams();

    /**
     * Construtor
     * 
     * @param controller controller
     */
    public ServiceRunner(ServicesController controller) {
        this.controller = controller;
        this.clearServicesPage();
        this.clearReferences();
    }

    /**
     * Disables inner transactions
     */
    public void disableInnerTransactions() {
        this.setExecutorParams(new ExecutorParams(ExecutorTransactionPolicy.NONE, ExecutorValidationPolicy.BEFORE_ANY_EXECUTION, false));
    }

    /**
     * @param executorParams the executorParams to set
     */
    public void setExecutorParams(ExecutorParams executorParams) {
        this.executorParams = executorParams;
    }

    /**
     * Limpa a página de serviços
     */
    public void clearServicesPage() {
        this.services = new ServicesPage();
    }

    /**
     * Limpa as referências
     */
    public void clearReferences() {
        this.parentProperties = new ServiceProperties(this.controller);
    }

    /**
     * Adiciona um novo serviço
     * 
     * @param builder builder
     * @return runner
     */
    public ServiceRunner addService(Builder<ServiceBean> builder) {
        this.closeService();
        this.current = builder.build();
        return this;
    }

    /**
     * Adiciona diversos novos serviços
     * 
     * @param beans beans
     * @return runner
     */
    public ServiceRunner addServices(List<ServiceBean> beans) {
        for (ServiceBean bean : beans) {
            this.addService(bean);
        }
        return this;
    }

    /**
     * Adiciona um novo serviço
     * 
     * @param bean bean
     * @return runner
     */
    public ServiceRunner addService(ServiceBean bean) {
        this.closeService();
        this.current = bean;
        return this;
    }

    /**
     * Inicia a construção de um serviço
     * 
     * @param action action
     * @param name name
     * @return ServiceRunner
     */
    public ServiceRunner startService(ActionsEnum action, String name) {
        this.closeService();
        this.current = new ServiceBean();
        this.current.setAction(action);
        this.current.setName(name);
        return this;
    }

    /**
     * Inicializa o serviço com a classe dada
     * 
     * @param serviceClass serviceClass
     * @return ServiceRunner
     */
    public ServiceRunner startService(Class<?> serviceClass) {
        return this.startService(ExportUtils.getServiceAction(serviceClass), ExportUtils.getServiceName(serviceClass));
    }

    /**
     * Adiciona o objeto ao contexto de execução pai, como aconteceria no caso de uma referência salva.
     * 
     * @param key key
     * @param value value
     * @return ServiceRunner
     */
    public ServiceRunner addReference(String key, Object value) {
        this.parentProperties.put(key, value);
        return this;
    }

    /**
     * Adiciona o parâmetro ao serviço sendo construído para execução.
     * 
     * @param key key
     * @param value value
     * @return ServiceRunner
     */
    public ServiceRunner addParameter(String key, String value) {
        if (this.current == null) {
            throw new IllegalStateException("Primeiro é necessário invocar startService para depois definir os parâmetros do serviço.");
        }
        this.current.getParameters().put(key, value);
        return this;
    }

    /**
     * Fecha o serviço criado, adicionando-o à lista para execução.
     * 
     * @return ServiceRunner
     */
    public ServiceRunner closeService() {
        if (this.current != null) {
            Map<String, Object> parameters = this.controller.instantiateMap();
            parameters.putAll(this.current.getParameters());
            ServiceProperties serviceProperties = new ServiceProperties(this.controller, this.parentProperties, parameters);
            try {
                this.services.addService(this.controller.getServiceFactory().getService(this.current.getAction(), this.current.getName(),
                        serviceProperties));
            } catch (ServiceCreationException e) {
                throw new PersistenceException(e.getLocalizedMessageHolder(), e);
            }
            this.current = null;
        }
        return this;
    }

    /**
     * Executa os serviços e, caso ocorra alguma exception, joga runtime.
     * 
     * @return ServiceProperties
     */
    public ServiceProperties run() {
        try {
            return this.checkedRun();
        } catch (ServiceExecutionException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Executa os serviços construídos
     * 
     * @throws ServiceExecutionException ServiceExecutionException
     */
    public ServiceProperties checkedRun() throws ServiceExecutionException {
        this.closeService();
        this.controller.getServicesPageExecutor().execute(Collections.singletonList(this.services), this.executorParams);
        ServiceProperties prop = this.parentProperties;
        this.clearServicesPage();
        return prop;
    }
}
